// src/utils/Canvas.ts

/**
 * Canvas 类：封装 HTML5 Canvas 的常用操作
 */

export default class Canvas {
  private canvas: HTMLCanvasElement; // 画布元素
  private ctx: CanvasRenderingContext2D; // 2D 渲染上下文

  /**
   * 构造函数
   * @param canvasId - canvas 元素的 id
   */
  constructor(canvasId: string) {
    const element = document.getElementById(canvasId);
    if (!(element instanceof HTMLCanvasElement)) {
      throw new Error(`Canvas with id "${canvasId}" not found or is not a canvas element.`);
    }
    this.canvas = element;
    const context = this.canvas.getContext('2d');
    if (!context) {
      throw new Error('当前浏览器不支持画布，请下载最新的浏览器');
    }
    this.ctx = context;
  }

  /**
   * 设置画布尺寸
   * @param width - 画布宽度
   * @param height - 画布高度
   * @param background - 画布背景
   */
  public setSize(width: number, height: number): void {
    this.canvas.width = width;
    this.canvas.height = height;
  }

  /**
   * 清除整个画布内容
   */
  public clear(): void {
    this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
  }
  /**
   * 清除阴影设置的方法
   */
  public clearShadow(): void {
    this.ctx.shadowColor = 'rgba(0,0,0,0)';
  }
  /**
   * 绘制多边形
   * @param options - 参数对象
   * @param points - 多边形顶点数组，每个元素为[x, y]坐标
   * @param strokeColor - 描边颜色
   * @param fillColor - 填充颜色
   * @param lineWidth - 线宽
   */
  public drawPolygon(options: { points: [number, number][]; strokeColor?: string; fillColor?: string; lineWidth?: number }): void {
    const { points, strokeColor = 'black', fillColor, lineWidth = 1 } = options;

    if (points.length < 3) {
      console.error('至少需要三个点来构成一个多边形');
      return;
    }

    this.ctx.beginPath();
    this.ctx.moveTo(points[0][0], points[0][1]);
    for (let i = 1; i < points.length; i++) {
      this.ctx.lineTo(points[i][0], points[i][1]);
    }
    this.ctx.closePath();

    if (fillColor) {
      this.ctx.fillStyle = fillColor;
      this.ctx.fill();
    }

    if (strokeColor) {
      this.ctx.strokeStyle = strokeColor;
      this.ctx.lineWidth = lineWidth;
      this.ctx.stroke();
    }
  }

  /**
   * 绘制二次贝塞尔曲线
   * @param options - 参数对象
   * @param startX - 起始点x坐标
   * @param startY - 起始点y坐标
   * @param cpX - 控制点x坐标
   * @param cpY - 控制点y坐标
   * @param endX - 结束点x坐标
   * @param endY - 结束点y坐标
   * @param strokeColor - 曲线颜色
   * @param lineWidth - 线宽
   */
  public drawQuadraticCurve(options: { startX: number; startY: number; cpX: number; cpY: number; endX: number; endY: number; strokeColor?: string; lineWidth?: number }): void {
    const { startX, startY, cpX, cpY, endX, endY, strokeColor = 'black', lineWidth = 1 } = options;

    this.ctx.beginPath();
    this.ctx.moveTo(startX, startY);
    this.ctx.quadraticCurveTo(cpX, cpY, endX, endY);

    this.ctx.strokeStyle = strokeColor;
    this.ctx.lineWidth = lineWidth;
    this.ctx.stroke();
  }

  /**
   * 绘制三次贝塞尔曲线
   * @param options - 参数对象
   * @param startX - 起始点x坐标
   * @param startY - 起始点y坐标
   * @param cp1X - 第一控制点x坐标
   * @param cp1Y - 第一控制点y坐标
   * @param cp2X - 第二控制点x坐标
   * @param cp2Y - 第二控制点y坐标
   * @param endX - 结束点x坐标
   * @param endY - 结束点y坐标
   * @param strokeColor - 曲线颜色
   * @param lineWidth - 线宽
   */
  public drawBezierCurve(options: {
    startX: number;
    startY: number;
    cp1X: number;
    cp1Y: number;
    cp2X: number;
    cp2Y: number;
    endX: number;
    endY: number;
    strokeColor?: string;
    lineWidth?: number;
  }): void {
    const { startX, startY, cp1X, cp1Y, cp2X, cp2Y, endX, endY, strokeColor = 'black', lineWidth = 1 } = options;

    this.ctx.beginPath();
    this.ctx.moveTo(startX, startY);
    this.ctx.bezierCurveTo(cp1X, cp1Y, cp2X, cp2Y, endX, endY);

    this.ctx.strokeStyle = strokeColor;
    this.ctx.lineWidth = lineWidth;
    this.ctx.stroke();
  }
  /**
   * 绘制椭圆
   * @param options - 椭圆参数
   * @param x - 椭圆中心x坐标
   * @param y - 椭圆中心y坐标
   * @param radiusX - 水平半径
   * @param radiusY - 垂直半径
   * @param rotation - 旋转角度（弧度）
   * @param startAngle - 起始弧度角
   * @param endAngle - 结束弧度角
   * @param counterclockwise - 是否逆时针绘制
   * @param fillColor - 填充颜色
   * @param strokeColor - 描边颜色
   * @param lineWidth - 描边宽度
   */
  public drawEllipse(options: {
    x?: number;
    y?: number;
    radiusX?: number;
    radiusY?: number;
    rotation?: number;
    startAngle?: number;
    endAngle?: number;
    counterclockwise?: boolean;
    fillColor?: string;
    strokeColor?: string;
    lineWidth?: number;
  }): void {
    const { x = 0, y = 0, radiusX = 50, radiusY = 30, rotation = 0, startAngle = 0, endAngle = Math.PI * 2, counterclockwise = false, fillColor, strokeColor = 'black', lineWidth = 1 } = options;

    this.ctx.beginPath();
    this.ctx.ellipse(x, y, radiusX, radiusY, rotation, startAngle, endAngle, counterclockwise);

    if (fillColor) {
      this.ctx.fillStyle = fillColor;
      this.ctx.fill();
    }

    if (strokeColor) {
      this.ctx.strokeStyle = strokeColor;
      this.ctx.lineWidth = lineWidth;
      this.ctx.stroke();
    }
  }
  /**
   * 绘制图片
   * @param options - 图片参数
   * @param image - HTMLImageElement 对象
   * @param x - 左上角x坐标
   * @param y - 左上角y坐标
   * @param width - 显示宽度（可选）
   * @param height - 显示高度（可选）
   */
  public drawImage(options: { image: HTMLImageElement; x?: number; y?: number; width?: number; height?: number }): void {
    const { image, x = 0, y = 0, width = image.width, height = image.height } = options;
    this.ctx.drawImage(image, x, y, width, height);
  }
  /**
   * 绘制自定义路径
   * @param options - 路径操作数组
   * @example
   * drawPath({
   *   path: [
   *     ['moveTo', 10, 10],
   *     ['lineTo', 100, 100],
   *     ['bezierCurveTo', 150, 50, 200, 150, 200, 100]
   *   ],
   *   strokeColor: 'red'
   * })
   */
  public drawPath(options: { path: Array<['moveTo' | 'lineTo' | 'bezierCurveTo' | 'quadraticCurveTo', ...number[]]>; strokeColor?: string; fillColor?: string; lineWidth?: number }): void {
    const { path, strokeColor = 'black', fillColor, lineWidth = 1 } = options;

    this.ctx.beginPath();

    for (const step of path) {
      const [command, ...args] = step;
      switch (command) {
        case 'moveTo':
          this.ctx.moveTo(...(args as [number, number]));
          break;
        case 'lineTo':
          this.ctx.lineTo(...(args as [number, number]));
          break;
        case 'bezierCurveTo':
          this.ctx.bezierCurveTo(...(args as [number, number, number, number, number, number]));
          break;
        case 'quadraticCurveTo':
          this.ctx.quadraticCurveTo(...(args as [number, number, number, number]));
          break;
      }
    }

    if (fillColor) {
      this.ctx.fillStyle = fillColor;
      this.ctx.fill();
    }

    if (strokeColor) {
      this.ctx.strokeStyle = strokeColor;
      this.ctx.lineWidth = lineWidth;
      this.ctx.stroke();
    }
  }
  /**
   * 绘制扇形
   * @param options - 扇形参数
   * @param x - 圆心x坐标
   * @param y - 圆心y坐标
   * @param radius - 半径
   * @param startAngle - 起始角度（弧度）
   * @param endAngle - 结束角度（弧度）
   * @param fillColor - 填充颜色
   * @param strokeColor - 描边颜色
   * @param lineWidth - 描边宽度
   */
  public drawSector(options: { x?: number; y?: number; radius?: number; startAngle?: number; endAngle?: number; fillColor?: string; strokeColor?: string; lineWidth?: number }): void {
    const { x = 0, y = 0, radius = 50, startAngle = 0, endAngle = Math.PI / 2, fillColor = 'blue', strokeColor = 'black', lineWidth = 1 } = options;

    this.ctx.beginPath();
    this.ctx.moveTo(x, y);
    this.ctx.arc(x, y, radius, startAngle, endAngle);
    this.ctx.closePath();

    if (fillColor) {
      this.ctx.fillStyle = fillColor;
      this.ctx.fill();
    }

    if (strokeColor) {
      this.ctx.strokeStyle = strokeColor;
      this.ctx.lineWidth = lineWidth;
      this.ctx.stroke();
    }
  }
  /**
   * 绘制虚线段
   * @param options - 参数对象
   * @param x - 起始点x
   * @param y - 起始点y
   * @param endX - 结束点x
   * @param endY - 结束点y
   * @param dashLength - 虚线段长度
   * @param gapLength - 虚线间隔长度
   * @param color - 颜色
   * @param lineWidth - 线宽
   */
  public drawDashedLine(options: { x?: number; y?: number; endX?: number; endY?: number; dashLength?: number; gapLength?: number; color?: string; lineWidth?: number }): void {
    const { x = 0, y = 0, endX = 100, endY = 100, dashLength = 10, gapLength = 5, color = 'black', lineWidth = 1 } = options;

    const dx = endX - x;
    const dy = endY - y;
    const distance = Math.sqrt(dx * dx + dy * dy);
    const numSegments = Math.floor(distance / (dashLength + gapLength));
    const angle = Math.atan2(dy, dx);

    this.ctx.beginPath();
    this.ctx.strokeStyle = color;
    this.ctx.lineWidth = lineWidth;

    let currentX = x;
    let currentY = y;

    for (let i = 0; i < numSegments; i++) {
      const x1 = currentX;
      const y1 = currentY;
      const x2 = currentX + Math.cos(angle) * dashLength;
      const y2 = currentY + Math.sin(angle) * dashLength;

      this.ctx.moveTo(x1, y1);
      this.ctx.lineTo(x2, y2);

      currentX += Math.cos(angle) * (dashLength + gapLength);
      currentY += Math.sin(angle) * (dashLength + gapLength);
    }

    this.ctx.stroke();
  }
  /**
   * 绘制箭头
   * @param options - 参数对象
   * @param startX - 起始x
   * @param startY - 起始y
   * @param endX - 结束x
   * @param endY - 结束y
   * @param headSize - 箭头大小
   * @param color - 颜色
   * @param lineWidth - 线宽
   */
  public drawArrow(options: { startX?: number; startY?: number; endX?: number; endY?: number; headSize?: number; color?: string; lineWidth?: number }): void {
    const { startX = 0, startY = 0, endX = 100, endY = 100, headSize = 10, color = 'black', lineWidth = 1 } = options;

    const dx = endX - startX;
    const dy = endY - startY;
    const angle = Math.atan2(dy, dx);

    // 主线段
    this.ctx.beginPath();
    this.ctx.strokeStyle = color;
    this.ctx.lineWidth = lineWidth;
    this.ctx.moveTo(startX, startY);
    this.ctx.lineTo(endX, endY);
    this.ctx.stroke();

    // 箭头部分
    this.ctx.beginPath();
    this.ctx.moveTo(endX, endY);
    this.ctx.lineTo(endX - headSize * Math.cos(angle - Math.PI / 6), endY - headSize * Math.sin(angle - Math.PI / 6));
    this.ctx.moveTo(endX, endY);
    this.ctx.lineTo(endX - headSize * Math.cos(angle + Math.PI / 6), endY - headSize * Math.sin(angle + Math.PI / 6));
    this.ctx.stroke();
  }
  /**
   * 绘制渐变矩形
   * @param options - 渐变矩形参数
   * @param x - 左上角x
   * @param y - 左上角y
   * @param width - 宽度
   * @param height - 高度
   * @param direction - 方向：horizontal | vertical
   * @param fromColor - 开始颜色
   * @param toColor - 结束颜色
   */
  public drawGradientRect(options: { x?: number; y?: number; width?: number; height?: number; direction?: 'horizontal' | 'vertical'; fromColor?: string; toColor?: string }): void {
    const { x = 0, y = 0, width = 100, height = 50, direction = 'horizontal', fromColor = 'red', toColor = 'blue' } = options;

    let gradient: CanvasGradient;

    if (direction === 'horizontal') {
      gradient = this.ctx.createLinearGradient(x, y, x + width, y);
    } else {
      gradient = this.ctx.createLinearGradient(x, y, x, y + height);
    }

    gradient.addColorStop(0, fromColor);
    gradient.addColorStop(1, toColor);

    this.ctx.fillStyle = gradient;
    this.ctx.fillRect(x, y, width, height);
  }
  /**
   * 绘制一个矩形
   * @param x - 矩形左上角 x 坐标
   * @param y - 矩形左上角 y 坐标
   * @param width - 矩形宽度
   * @param height - 矩形高度
   * @param fillColor - 填充颜色（可选）
   * @param strokeColor - 描边颜色（可选）
   * @param lineWidth - 描边线宽（可选）
   */

  public drawRect(options: {
    x?: number;
    y?: number;
    width?: number;
    height?: number;
    fillColor?: string;
    strokeColor?: string;
    lineWidth?: number;
    shadowColor?: string;
    shadowBlur?: number;
    shadowOffsetX?: number;
    shadowOffsetY?: number;
  }): void {
    const { x = 0, y = 0, width = 0, height = 0, fillColor, strokeColor, lineWidth = 1, shadowColor, shadowBlur = 20, shadowOffsetX = 0, shadowOffsetY = 0 } = options;

    // 验证必要参数
    if (width <= 0 || height <= 0) {
      console.error('矩形宽度和高度必须大于0');
      return;
    }

    // 设置阴影相关属性
    if (shadowColor) {
      this.ctx.shadowColor = shadowColor;
      this.ctx.shadowBlur = shadowBlur;
      this.ctx.shadowOffsetX = shadowOffsetX;
      this.ctx.shadowOffsetY = shadowOffsetY;
    } else {
      this.clearShadow();
    }

    // 先画填充（如果有）
    if (fillColor) {
      this.ctx.fillStyle = fillColor;
      this.ctx.fillRect(x, y, width, height);
    }

    // 再画描边（如果有）
    if (strokeColor) {
      this.ctx.strokeStyle = strokeColor;
      this.ctx.lineWidth = lineWidth;
      this.ctx.strokeRect(x, y, width, height);
    }
  }

  /**
   * 获取当前画布的 2D 上下文
   * @returns CanvasRenderingContext2D
   */
  public getContext(): CanvasRenderingContext2D {
    return this.ctx;
  }
  /**
   * 示例方法：绘制文本
   * @param text - 要绘制的文本
   * @param x - 文本起始 x 坐标
   * @param y - 文本起始 y 坐标
   * @param color - 文本颜色
   * @param fontSize - 字体大小
   * @param fontFamily - 字体类型
   */
  public drawText(options: {
    type: 'text'; //
    text: string;
    x?: number;
    y?: number;
    width?: number;
    height?: number;
    color?: string;
    fontSize?: string;
    fontFamily?: number;
  }): void {
    const { text = 'text', x = 0, y = 0, color = 'black', fontSize = 16, fontFamily = 'Arial' } = options;
    this.ctx.fillStyle = color;
    this.ctx.font = `${fontSize}px ${fontFamily}`;
    this.ctx.fillText(text, x, y);
  }
  /**
   * 示例方法：绘制线段
   * @param options - 参数。
   * @param x - 线条起始点的X坐标，默认为0。
   * @param y - 线条起始点的Y坐标，默认为0。
   * @param lines - 由[x, y]坐标组成的数组，定义了要连接的点。。
   */
  public drawLine(options: {
    x?: number; //
    y?: number;
    lines?: [number, number][];
    lineWidth?: number;
    strokeColor?: string;
  }): void {
    // 解构赋值获取options中的属性，并设置默认值
    const { x = 0, y = 0, lines = [], lineWidth = 1, strokeColor = 'black' } = options;
    // 设置线条样式
    this.ctx.strokeStyle = strokeColor; // 设置线条颜色
    this.ctx.lineWidth = lineWidth; // 设置线条宽度
    // 开始一条新的路径
    this.ctx.beginPath();

    // 将路径起点移动到(x, y)
    this.ctx.moveTo(x, y);

    // 遍历所有给定的点，将其加入当前子路径
    lines.forEach((point: any[]) => {
      this.ctx.lineTo(point[0], point[1]);
    });

    // 如果提供了多于一个点，则将最后一个点与起始点相连形成闭合图形
    if (lines.length > 1) {
      this.ctx.lineTo(x, y);
    }

    // 绘制路径
    this.ctx.stroke();

    // 结束当前路径
    this.ctx.closePath();
  }
  /**
   * 绘制圆或圆弧
   * @param x - 圆心x坐标
   * @param y - 圆心y坐标
   * @param radius - 圆的半径
   * @param startAngle - 开始角度（以弧度为单位）
   * @param endAngle - 结束角度（以弧度为单位）
   * @param direction - 绘制方向，true为顺时针，false为逆时针
   * @param lineWidth  - 绘制方向，true为顺时针，false为逆时针
   * @param strokeColor   - 绘制方向，true为顺时针，false为逆时针
   */
  public drawCircle(options: {
    x?: number; //
    y?: number;
    radius?: number;
    startAngle?: number;
    endAngle?: number;
    direction?: boolean;
    lineWidth?: number;
    strokeColor?: string;
    fillColor?: string;
  }): void {
    const { x = 0, y = 0, radius = 0, startAngle = 0, endAngle = Math.PI * 2, direction = false, lineWidth = 1, strokeColor, fillColor } = options;
    this.ctx.beginPath();
    // 设置线条的粗细
    if (lineWidth) {
      this.ctx.lineWidth = lineWidth;
    }

    // 设置线条的颜色
    if (strokeColor) {
      this.ctx.strokeStyle = strokeColor;
    }

    // 根据给定的方向设置arc方法的最后一个参数
    // 注意：这里的 direction 参数与arc函数期望的参数值相反，
    // 因此我们使用了!direction来转换预期的布尔值。
    this.ctx.arc(x, y, radius, startAngle, endAngle, direction);
    if (fillColor) {
      this.ctx.fillStyle = fillColor;
      this.ctx.fill();
    }
    // 绘制路径
    this.ctx.stroke();

    // 可选：关闭路径，防止后续绘制操作与当前路径混淆
    this.ctx.closePath();
  }

  /**
   * 示例方法：绘制组
   * @param options - 要绘制的数组
   */
  public draw(options: any[]): void {
    this.clear();
    const _this = this;
    const _sun = (arr: any) => {
      arr.forEach((item: any) => {
        if (item.children) {
          _sun(item.children);
        } else {
          _this.ctx.save(); // 保存当前上下文状态
          switch (item.type) {
            case 'rect':
              this.drawRect(item);
              break;
            case 'text':
              this.drawText(item);
              break;
            case 'circle':
              this.drawCircle(item);
              break;
            case 'line':
              this.drawLine(item);
              break;
            case 'polygon':
              this.drawPolygon(item);
              break;
            case 'quadraticCurve':
              this.drawQuadraticCurve(item);
              break;
            case 'bezierCurve':
              this.drawBezierCurve(item);
              break;
            case 'ellipse':
              this.drawEllipse(item);
              break;
            case 'image':
              this.drawImage(item);
              break;
            case 'path':
              this.drawPath(item);
              break;
            case 'sector':
              this.drawSector(item);
              break;
            case 'dashedLine':
              this.drawDashedLine(item);
              break;
            case 'arrow':
              this.drawArrow(item);
              break;
            case 'gradientRect':
              this.drawGradientRect(item);
              break;
            default:
              console.warn('未找到对应图形类型:', item.type);
          }
          _this.ctx.restore(); // 恢复到之前的状态
        }
      });
    };
    _sun(options);
  }
}
